//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 08/01/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog.  If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#import "note_recogTests.h"
#import <vector>
#import <iostream>
#import "FastFourierTransform.h"
#import "Condenser.h"
#import "TestData.h"
#import "Slice.h"
#import "IConsumer.h"
#import <QuartzCore/CABase.h>
#import "Rememberer.h"
#import "INoteEvent.h"
#import <sstream>
#import "dirent.h"
#import <fstream>
#import "Spectrum.h"
#import "Harmonic.h"
#import "Optimiser.h"

@implementation note_recogTests

- (void)setUp
{
    [super setUp];
    
    // Set-up code here.
}

- (void)tearDown
{
    // Tear-down code here.
    
    [super tearDown];
}

//-(void) testA440Repeated
//{
//    const Rememberer::Notes notes = Rememberer::playDataIntoCondenser( "a440repeated22khz", Condenser::InitInfo() );
//    
//    STAssertEquals(11ul, notes.size(), @"note count");
//    
//    return;
//    
//    // skip first note: it's from quiet noise
//    for( Rememberer::Notes::const_iterator it = ++(notes.begin());
//        it != notes.end();
//        ++it) {
//        STAssertEqualsWithAccuracy(440.f, it->second, 7.f, @"bad frequency" );
//    }
//}

-(void) testMonotonicWobble
{
    const Rememberer::Notes notes = Rememberer::playDataIntoCondenser( "a440monotonic22khz", Condenser::InitInfo() );
    
    STAssertEquals(5ul, notes.size(), @"note count");
    
    for( const Rememberer::Notes::value_type& note : notes ) {
        STAssertEqualsWithAccuracy(440.f, note.second, 7.f, @"bad frequency" );
    }
}

//-(void) testChromatic
//{
//    const Rememberer::Notes notes = Rememberer::playDataIntoCondenser( "fastChromatic22khz", Condenser::InitInfo() );
//
//    STAssertTrue(notes.size() >= 6ul, @"note count too low");
//    STAssertTrue(notes.size() <= 20ul, @"note count too high");
//
//    float lastFreq = 0.f;
//    Rememberer::Notes::const_iterator it = notes.begin();
//    ++it; ++it; ++it; ++it; //skip first four (they're noise).
//    for( ; it != notes.end(); ++it) {
//        const float freq = it->second;
//        STAssertTrue( freq > lastFreq, @"not increasing" );
//        lastFreq = freq;
//    }
//}

//-(void) testSlowChromatic
//{
//    const Rememberer::Notes notes = Rememberer::playDataIntoCondenser( "slowChromatic22khz", Condenser::InitInfo() );
//
//    STAssertEquals(19ul, notes.size(), @"note count");
//    
//    float lastFreq = 0.f;
//    Rememberer::Notes::const_iterator it = notes.begin();
//    ++it; //skip first (it's noise).
//    for( ; it != notes.end(); ++it) {
//        const float freq = it->second;
//        STAssertTrue( freq > lastFreq, @"not increasing" );
//        lastFreq = freq;
//    }
//}

//-(void) testFastStaccatoChromatic
//{
//    const Rememberer::Notes notes
//        = Rememberer::playDataIntoCondenser( "fastStaccatoChromatic22khz", Condenser::InitInfo() );
//    
//    STAssertEquals(7ul, notes.size(), @"note count");
//    
//    float lastFreq = 0.f;
//    Rememberer::Notes::const_iterator it = notes.begin();
//    ++it; //skip first (it's noise).
//    for( ; it != notes.end(); ++it) {
//        const float freq = it->second;
//        std::ostringstream oss;
//        oss << "not increasing: " << freq << " is not greater than " << lastFreq 
//            << " at " << it->first << "s";
//        const std::string desc = oss.str();
//        NSString __strong * nsstring = [NSString stringWithUTF8String:desc.c_str()];
//        STAssertTrue( freq > lastFreq, nsstring );
//        lastFreq = freq;
//    }
//}

static Rememberer::Notes getFastChromaticNotes()
{
    Rememberer::Notes notes;
    notes[1.30] = 261.62; // C
    notes[1.50] = 277.18; // C#
    notes[1.75] = 293.66; // D
    notes[2.00] = 311.12; // D# (not from Jane)
    notes[2.20] = 329.62; // E
    notes[2.40] = 349.22; // F
    notes[2.60] = 369.99; // F#
    notes[2.90] = 391.99; // G
    notes[3.10] = 415.30; // G#
    notes[3.40] = 440.00; // A
    notes[3.60] = 466.16; // A# (main)
    notes[3.85] = 493.88; // B
    notes[4.10] = 523.25; // C
    notes[4.30] = 554.36; // C#
    notes[4.60] = 587.32; // D
    notes[4.80] = 622.25; // D#
    notes[5.00] = 659.25; // E
    notes[5.30] = 698.45; // F
    notes[5.50] = 739.98; // F#
    return notes;
}

static Rememberer::Notes getSlowChromaticNotes( )
{
    Rememberer::Notes notes;
    notes[0.90]  = 195.99; // G
    notes[1.80]  = 207.65; // G#
    notes[2.60]  = 220.00; // A
    notes[3.40]  = 233.08; // A#
    notes[4.30]  = 246.94; // B
    notes[5.30]  = 261.62; // C
    notes[6.00]  = 277.18; // C#
    notes[6.90]  = 293.66; // D
    notes[7.30]  = 311.12; // D# (not from Jane)
    notes[7.70]  = 329.62; // E
    notes[8.60]  = 349.22; // F
    notes[9.40]  = 369.99; // F#
    notes[10.20] = 391.99; // G
    notes[11.00] = 415.30; // G#
    notes[11.80] = 440.00; // A
    notes[12.90] = 466.16; // A# (extra)
    notes[13.50] = 466.16; // A# (main)
    notes[13.50] = 493.88; // B
    notes[15.20] = 523.25; // C
    notes[16.00] = 554.36; // C#
    notes[16.90] = 587.32; // D
    notes[17.80] = 622.25; // D#
    notes[18.70] = 659.25; // E
    notes[19.50] = 698.45; // F
    notes[20.40] = 739.98; // F#
    notes[21.30] = 783.99; // G
    return notes;
}

-(float) checkSlowChromaticComprehensive
{
    const Rememberer::Notes expected = getSlowChromaticNotes();
    Rememberer rem( expected );
    rem.playDataViaCondenser( "logged_slow_chromatic_20", Condenser::InitInfo() );
    return rem.validate();
}

-(float) checkFastChromaticComprehensive
{
    const Rememberer::Notes expected = getFastChromaticNotes();
    Rememberer rem( expected );
    rem.playDataViaCondenser( "logged_fast_chromatic_24", Condenser::InitInfo() );
    return rem.validate();
}

-(void) testSlowChromaticComprehensive
{
    const unsigned int errorCount = [self checkSlowChromaticComprehensive];
    STAssertEquals(
        errorCount,
        0u,
        @"missing or extra notes" );
}

-(void) testFastChromaticComprehensive
{
    const unsigned int errorCount = [self checkFastChromaticComprehensive];
    STAssertTrue(
        errorCount <= 1u,
        @"too many missing or extra notes" );
}

static float singleIteration(
                             const Optimiser::single_input& input,
                             const std::map<std::string,Rememberer::Notes>& name2expectedNotes)
{
    Condenser::InitInfo info;
    
    info.setMinQuality(
        input.find("minQuality")->second);
    
    info.setDecayTimeConstant(
        input.find("decayTimeConstant")->second);
    
    info.setTransientBlackoutDuration(
                                      input.find(
                                                 "transientBlackoutDuration")->second);
    
    float score = 0.f;
    
    for( const std::map<std::string,Rememberer::Notes>::value_type& it : name2expectedNotes )
    {
        const std::string& name = it.first;
        const Rememberer::Notes& expectedNotes = it.second;
        Rememberer rem( expectedNotes );
        rem.playDataViaCondenser( name.c_str(), info );
        score += -static_cast<float>( rem.validate() );
    }
    
    return score;
}

static float fastChromaticIteration( const Optimiser::single_input& input )
{
    std::map<std::string,Rememberer::Notes> name2expectedNotes;
    name2expectedNotes["logged_fast_chromatic_24"] = getFastChromaticNotes();
    return singleIteration(input,name2expectedNotes);
}

static float slowChromaticIteration( const Optimiser::single_input& input )
{ 
    std::map<std::string,Rememberer::Notes> name2expectedNotes;
    name2expectedNotes["logged_slow_chromatic_20"] = getSlowChromaticNotes();
    return singleIteration(input,name2expectedNotes);
}

static float bothChromaticIteration( const Optimiser::single_input& input )
{ 
    std::map<std::string,Rememberer::Notes> name2expectedNotes;
    name2expectedNotes["logged_fast_chromatic_24"] = getFastChromaticNotes();
    name2expectedNotes["logged_slow_chromatic_20"] = getSlowChromaticNotes();
    return singleIteration(input,name2expectedNotes);
}

static void optimise( Optimiser::target_func singleIterationFunc )
{
    Optimiser::arg_range transient("transientBlackoutDuration");
    transient.setUp(0.05f, 33.f, 2.5f);
    transient.ignoreLowerBoundPerimeterTouch() = true;
    std::cout << transient << std::endl;
    
    Optimiser::arg_range quality("minQuality");
    quality.setUp(2.f, 10.f, 1.5f);
    quality.ignoreLowerBoundPerimeterTouch() = true;
    std::cout << quality << std::endl;
    
    Optimiser::arg_range decay("decayTimeConstant");
    decay.setUp(0.1f, 10.f, 1.5f);
    decay.ignoreLowerBoundPerimeterTouch() = true;
    std::cout << decay << std::endl;
    
    std::cout   << "There are " << quality.size() 
                << " * " << decay.size() 
                << " * " << transient.size()
                << " = " << quality.size() * decay.size() * transient.size()
                << " iterations to perform."
                << std::endl;
    
    Optimiser::input_ranges ranges;
    ranges.add(quality);
    ranges.add(decay);
    ranges.add(transient);
    
    Optimiser opt(singleIterationFunc,ranges);
    Optimiser::input_set inputOpt;
    const float peak = opt.getPeak(&inputOpt);
    std::cout << "Peak result = " << peak << std::endl;
    std::cout << "Optimsed input = " << inputOpt << std::endl;
    std::cout << "Centroid = " << inputOpt.centroid() << std::endl;
}

-(void) testOptimizationBoth
{    
    optimise(bothChromaticIteration);
}

-(void) testOptimizationFast
{    
    optimise(fastChromaticIteration);
}

-(void) testOptimizationSlow
{    
    optimise(slowChromaticIteration);
}

@end
